#!/usr/bin/env python3

import os

import common

class BaremetalComponent(common.BuildComponent):
    def do_build(self, args):
        common.assert_crosstool_ng_supports_arch(args.arch)
        build_dir = self.get_build_dir(args)
        bootloader_obj = os.path.join(common.baremetal_build_lib_dir, 'bootloader{}'.format(common.obj_ext))
        common_basename_noext = 'common'
        common_src = os.path.join(common.root_dir, common_basename_noext + common.c_ext)
        common_obj = os.path.join(common.baremetal_build_lib_dir, common_basename_noext + common.obj_ext)
        syscalls_basename_noext = 'syscalls'
        syscalls_src = os.path.join(common.baremetal_src_lib_dir, syscalls_basename_noext + common.c_ext)
        syscalls_obj = os.path.join(common.baremetal_build_lib_dir, syscalls_basename_noext + common.obj_ext)
        common_objs = [common_obj, syscalls_obj]
        cflags = [
            '-I', common.baremetal_src_lib_dir, common.Newline,
            '-I', common.root_dir, common.Newline,
            '-O0', common.Newline,
            '-ggdb3', common.Newline,
            '-mcpu={}'.format(common.mcpu), common.Newline,
            '-nostartfiles', common.Newline,
        ]
        if args.prebuilt:
            gcc = 'arm-none-eabi-gcc'
        else:
            os.environ['PATH'] = common.crosstool_ng_bin_dir + os.environ['PATH']
            gcc = common.get_toolchain_tool('gcc', allowed_toolchains=['crosstool-ng'])
        if common.emulator == 'gem5':
            if common.machine == 'VExpress_GEM5_V1':
                entry_address = 0x80000000
                uart_address = 0x1c090000
            elif common.machine == 'RealViewPBX':
                entry_address = 0x10000
                uart_address = 0x10009000
            else:
                raise Exception('unknown machine: ' + common.machine)
            cflags.extend(['-D', 'GEM5'.format(uart_address), common.Newline])
        else:
            entry_address = 0x40000000
            uart_address = 0x09000000
        os.makedirs(build_dir, exist_ok=True)
        os.makedirs(common.baremetal_build_lib_dir, exist_ok=True)
        common.run_cmd(
            [gcc,  common.Newline] +
            cflags +
            [
                '-c', common.Newline,
                '-o', bootloader_obj, common.Newline,
                os.path.join(common.baremetal_src_lib_dir, '{}{}'.format(args.arch, common.asm_ext)), common.Newline,
            ]
        )
        for src, obj in [
            (common_src, common_obj),
            (syscalls_src, syscalls_obj),
        ]:
            common.run_cmd(
                [gcc,  common.Newline] +
                cflags +
                [
                    '-c', common.Newline,
                    '-D', 'UART0_ADDR={:#x}'.format(uart_address), common.Newline,
                    '-o', obj, common.Newline,
                    src, common.Newline,
                ]
            )
        self._build_dir(
            '',
            gcc=gcc,
            cflags=cflags,
            entry_address=entry_address,
            bootloader_obj=bootloader_obj,
            common_objs=common_objs,
        )
        self._build_dir(
            'interactive',
            gcc=gcc,
            cflags=cflags,
            entry_address=entry_address,
            bootloader_obj=bootloader_obj,
            common_objs=common_objs,
        )
        arch_dir = os.path.join('arch', args.arch)
        if os.path.isdir(os.path.join(common.baremetal_src_dir, arch_dir)):
            self._build_dir(
                arch_dir,
                gcc=gcc,
                cflags=cflags,
                entry_address=entry_address,
                bootloader_obj=bootloader_obj,
                common_objs=common_objs,
            )
        arch_dir = os.path.join('arch', args.arch, 'no_bootloader')
        if os.path.isdir(os.path.join(common.baremetal_src_dir, arch_dir)):
            self._build_dir(
                arch_dir,
                gcc=gcc,
                cflags=cflags,
                entry_address=entry_address,
                bootloader_obj=bootloader_obj,
                common_objs=common_objs,
                bootloader=False,
            )

    def get_argparse_args(self):
        return {
            'description': '''\
Build the baremetal examples with crosstool-NG.
'''
        }

    def get_build_dir(self, args):
        return common.baremetal_build_dir

    def get_default_args(self):
        return {'baremetal': 'all'}

    def _build_dir(
            self,
            subpath,
            gcc,
            cflags,
            entry_address,
            bootloader_obj,
            common_objs,
            bootloader=True
        ):
        """
        Build all .c and .S files in a given subpath of the baremetal source
        directory non recursively.

        Place outputs on the same subpath or the output directory.
        """
        in_dir = os.path.join(common.baremetal_src_dir, subpath)
        out_dir = os.path.join(common.baremetal_build_dir, subpath)
        os.makedirs(out_dir, exist_ok=True)
        if bootloader:
            bootloader_cmd = [bootloader_obj, common.Newline]
        else:
            bootloader_cmd = []
        for in_basename in os.listdir(in_dir):
            in_path = os.path.join(in_dir, in_basename)
            if os.path.isfile(in_path) and os.path.splitext(in_basename)[1] in (common.c_ext, common.asm_ext):
                in_name = os.path.splitext(in_basename)[0]
                main_obj = os.path.join(common.baremetal_build_dir, subpath, '{}{}'.format(in_name, common.obj_ext))
                common.run_cmd(
                    [gcc,  common.Newline] +
                    cflags +
                    [
                        '-c', common.Newline,
                        '-o', main_obj, common.Newline,
                        os.path.join(common.baremetal_src_dir, in_path), common.Newline,
                    ]
                )
                common.run_cmd(
                    [gcc,  common.Newline] +
                    cflags +
                    [
                        '-Wl,--section-start=.text={:#x}'.format(entry_address), common.Newline,
                        '-o', os.path.join(common.baremetal_build_dir, subpath, in_name + common.baremetal_build_ext), common.Newline,
                        '-T', os.path.join(common.baremetal_src_dir, 'link.ld'), common.Newline,
                    ] +
                    bootloader_cmd +
                    common.add_newlines(common_objs) +
                    [
                        main_obj, common.Newline,
                    ]
                )

if __name__ == '__main__':
    BaremetalComponent().main()
